Chapter 21
File I/O and MFC

by K. David White

In This Chapter

  The CFile Class 758
  Inside the CFile Class 761
  The CFileDialog class 770
  The User-Defined CFileDialog class 772
  Practical Usage of CFile and CFileDialog 773
  A Classical Approach 778

File-based computing has been around for ages, or so it seems. Long before databases became the predominant data persistence mechanism, data was stored in files that resided on system disk. Although the database is currently persisted to a file system, the user is not concerned about how this file system is represented. There are many forms of data, and by the nature of the beast, it is apparent that binary representation of data in a file system presents a whole new set of problems.

Most applications use the file system in some fashion, and it is important for the MFC developer to understand what the file system represents as well as how to use it. In this chapter, you will discover that MFC provides a consistent interface, CFile, for handling data persistence to the file system. You’ll also take a cursory look at serialization and what takes place under the covers there.

These classes are straightforward and online documentation is fairly adequate, but I want to take an in-depth look at how it fits together. When you understand how the file system is put together, you will better understand how to use it.

The CFile Class

The MFC library provides the CFile class to handle normal I/O processing to the file system. This class provides basic nonbuffered file access that essentially wraps the Windows file API calls. There are derived classes of CFile that are used specifically for doing the file-related work. These are CStdioFile and CMemFile. The CStdioFile handles general I/O processing for ASCII type data buffers. The CMemFile handles processing for memory data.


Note:  

Serialization is another method to write information from the application to a file. Although this is primarily a function of the document/view architecture, I will take a look at it in this chapter. The MFC CDocument object framework handles serialization for you. It uses the CFile object. To take advantage of this, implement code in the Serialize member function of your derived CDocument object to process input and output.


The CFile class provides an interface for general-purpose binary file operations. The CStdioFile and CMemFile classes derived from CFile and the CSharedFile class derived from CMemFile supply more specialized file services.

A CStdioFile class represents stream-based file processing as opened by fopen. Stream files can be either text files or binary files, but the CStdioFile class handles the input and output in stream mode. The last section of this chapter provides some insight into file stream operations.


Note:  

The Duplicate, LockRange, and UnlockRange methods of CFile are not supported in CStdioFile.


Text mode, as with ASCII files, handles the carriage return-linefeed pair as an end-of-line. The newline character (0x0A), when applied to a string to be sent to the text-mode CStdioFile object, is actually written out as the byte pair (0x0A, 0x0D)(CR/LF). When that pair is read in, it translates it back to the newline character.

Let’s take a look inside CFile!

Processing Files with CFile

This section takes a general look at processing file data using the CFile-derived classes.

To use CFile, you first must create a CFile with a filename and indicate how the file is to be opened (that is, its access mode, including privilege settings). With the CFile class, you have the option of defining the privileges either in the constructor, or when the file is opened. Table 21.1 is a list of possible modes. Modes define the access privileges for handling the file operations.

Table 21.1 File Modes

Mode Description/Notes

modeRead Opens the file for reading only.
modeWrite Opens the file for writing.
modeReadWrite Opens the file for reading and writing.
modeCreate Will create the file. If the file already exists, the size will be set to zero.
modeNoTruncate Creates the file without truncating it.
modeNoInherit Prevents the file from being inherited by a child process.
shareDenyRead Denies Read access to the file from other applications/processes.
shareDenyWrite Denies Write access to the file from other applications/processes.
shareDenyNone Does not apply any sharing restrictions to the file.
shareExclusive Denies Read and Write access to the file from other applications/processes.
shareCompat Allows other processes to open the file any number of times.
typeBinary Sets the binary mode for the file. This mode is exclusive to the CStdioFile class.
typeText Sets the text mode for the file. This mode is exclusive to the CStdioFile class.



There are many member functions of CFile that let the developer perform a myriad of functions to a file. These are presented fairly well in the online documentation for Visual C++. However, let’s take a quick tour of some of the more commonly used functions. Table 21.2 presents my list of the functions that you as a developer should become familiar with.

Table 21.2 CFile Member Functions (Short List)

Mode Description/Notes

Open Opens a file.
Close Closes a file.
Read Reads data from a file based on a position in the file.
Write Writes data to a file based on a position in the file.
SeekToBegin Positions the file pointer to the beginning of the file.
SeekToEnd Positions to the end of a file.
GetLength Returns the length of the file.
SetLength Specifies the length of the file.
GetPosition Returns the current file position pointer.
GetStatus Returns the status of the file (more about this later).
GetFileName Returns a string representing the filename for the file.
GetFileTitle Returns the selected title of a file.
GetFilePath Returns the path associated with a file.
Rename Renames the file.
Remove Removes the file (delete).
GetStatus Returns the status of the file.

As most of us develop our skills as software developers, we become increasingly aware of the multitude of protection and access rights placed on files. You first look at file processing as simple input and output of text data. If you are like me, you have a tendency to overlook the necessary functions that prepare the file to do the work you need it to do. When processing files, it’s important to understand how the file is represented and what state it may be in. If you look inside the GetStatus method, you see that it takes a structure called CFileStatus. This structure, shown in Listing 21.1, provides a mechanism to query the file for its status prior to using it.

Listing 21.1 The CFileStatus Structure Represented in the MFC Header File AFX.H


/////////////////////////////////////////////////////////////////////
// File status

struct CFileStatus
{
   CTime m_ctime;    // creation date/time of file
   CTime m_mtime;    // last modification date/time of file
   CTime m_atime;    // last access date/time of file
   LONG m_size;      // logical size of file in bytes
   BYTE m_attribute; // logical OR of CFile::Attribute enum values
   BYTE _m_padding;  // pad the structure to a WORD
   TCHAR m_szFullName[_MAX_PATH]; // absolute pathname

#ifdef _DEBUG
void Dump(CDumpContext& dc) const;
#endif
};


As you can see from Listing 21.1, the file status structure contains some useful information. It contains a creation time, modification time, and an access time variable. These time variables, along with the size and attributes, determine when and how the file was used.



Inside the CFile Class

The CFile declaration resides in the AFX.H file, as shown in Listing 21.2.

Listing 21.2 The CFile Declaration (AFX.H)


01:    //////////////////////////////////////////////////////////////
02:    // File - raw unbuffered disk file I/O
03:
04:    class CFile : public CObject
05:    {
06:        DECLARE_DYNAMIC(CFile)
07:
08:    public:
09:    // Flag values
10:        enum OpenFlags {
11:            modeRead =          0×0000,
12:            modeWrite =         0×0001,
13:            modeReadWrite =     0×0002,
14:            shareCompat =       0×0000,
15:            shareExclusive =    0×0010,
16:            shareDenyWrite =    0×0020,
17:            shareDenyRead =     0×0030,
18:            shareDenyNone =     0×0040,
19:            modeNoInherit =     0×0080,
20:            modeCreate =        0×1000,
21:            modeNoTruncate =    0×2000,
22:            typeText =          0×4000, // typeText and typeBinary
               Äare used in
23:            typeBinary =   (int)0×8000 // derived classes
               Äonly
24:            };
25:
26:        enum Attribute {
27:            normal =    0×00,
28:            readOnly =  0×01,
29:            hidden =    0×02,
30:            system =    0×04,
31:            volume =    0×08,
32:            directory = 0×10,
33:            archive =   0×20
34:            };
35:
36:        enum SeekPosition { begin = 0×0, current = 0×1,
           Äend = 0×2 };
37:
38:        enum { hFileNull = -1 };
39:
40:    // Constructors
41:        CFile();
42:        CFile(int hFile);
43:        CFile(LPCTSTR lpszFileName, UINT nOpenFlags);
44:
45:    // Attributes
46:        UINT m_hFile;
47:        operator HFILE() const;
48:
49:        virtual DWORD GetPosition() const;
50:        BOOL GetStatus(CFileStatus& rStatus) const;
51:        virtual CString GetFileName() const;
52:        virtual CString GetFileTitle() const;
53:        virtual CString GetFilePath() const;
54:        virtual void SetFilePath(LPCTSTR lpszNewName);
55:
56:    // Operations
57:        virtual BOOL Open(LPCTSTR lpszFileName, UINT nOpenFlags,
58:            CFileException* pError = NULL);
59:
60:        static void PASCAL Rename(LPCTSTR lpszOldName,
61:                    LPCTSTR lpszNewName);
62:        static void PASCAL Remove(LPCTSTR lpszFileName);
63:        static BOOL PASCAL GetStatus(LPCTSTR lpszFileName,
64:                    CFileStatus& rStatus);
65:        static void PASCAL SetStatus(LPCTSTR lpszFileName,
66:                    const CFileStatus& status);
67:
68:        DWORD SeekToEnd();
69:        void SeekToBegin();
70:
71:        // backward compatible ReadHuge and WriteHuge
72:        DWORD ReadHuge(void* lpBuffer, DWORD dwCount);
73:        void WriteHuge(const void* lpBuffer, DWORD dwCount);
74:
75:    // Overridables
76:        virtual CFile* Duplicate() const;
77:
78:        virtual LONG Seek(LONG lOff, UINT nFrom);
79:        virtual void SetLength(DWORD dwNewLen);
80:        virtual DWORD GetLength() const;
81:
82:        virtual UINT Read(void* lpBuf, UINT nCount);
83:        virtual void Write(const void* lpBuf, UINT nCount);
84:
85:        virtual void LockRange(DWORD dwPos, DWORD dwCount);
86:        virtual void UnlockRange(DWORD dwPos, DWORD dwCount);
87:
88:        virtual void Abort();
89:        virtual void Flush();
90:        virtual void Close();
91:
92:    // Implementation
93:    public:
94:        virtual ~CFile();
95:    #ifdef _DEBUG
96:        virtual void AssertValid() const;
97:        virtual void Dump(CDumpContext& dc) const;
98:    #endif
99:        enum BufferCommand { bufferRead, bufferWrite,
           ÄbufferCommit, bufferCheck };
100:        virtual UINT GetBufferPtr(UINT nCommand, UINT nCount = 0,
101:            void** ppBufStart = NULL, void** ppBufMax = NULL);
102:
103:    protected:
104:        BOOL m_bCloseOnDelete;
105:        CString m_strFileName;
106:    };


Wow! What a declaration! Notice lines 9 through 34. These enum structures define not only the mode for the file, but also the attribute. You might be wondering what the primary difference is here. The attribute indicates the file’s current status, where the modes are used to define how the file can be accessed or shared. Reference the comment that Microsoft provides for you in this listing. The modes are given the member name of OpenFlags.

Now jump to line 99. This line contains an enum declaration for defining access privileges for CFile’s buffer. Confused yet? Specifically, CMemFile (and CArchive) use the buffer modes when processing shared memory buffers. If you are interested in looking at some code, the GetBufferPtr routine of CMemFile uses this enum structure when processing the buffer pointers (this is located in the Filemem.cpp file, which is supplied with MFC).

If you were to spend time looking at code for CFile functions, you would soon discover that it is nothing more than a wrapper for the Windows API functions. So to prevent a walkthrough of the MFC code, Table 21.3 can be of some value to you.

Table 21.3 CFile to Windows API Function Map

MFC CFile Function Windows API Function

Open() ::Createfile()
Close() ::CloseHandle()
Duplicate() ::DuplicateHandle()
Read() ::ReadFile()
Write() ::WriteFile()
Seek() ::SetFilePointer()
GetPosition() ::SetFilePointer()
SetLength() ::SetEndOfFile()
Rename() ::MoveFile()
Remove() ::DeleteFile()
GetStatus() ::GetFileTime(), GetFileSize()
LockRange() ::LockFile()
UnlockRange() ::UnlockFile()
Flush() ::FlushFileBuffers()



In addition, Microsoft sometimes provides undocumented helper functions you are free to use, if you accept the risk that the helper functions might be renamed or might even disappear altogether in future versions of MFC. That said, the following list shows filename helper functions that are available through the CFile implementation in FILECORE.CPP.

  AfxResolveShortCut()—This helper function looks up a Windows 95/98/NT 4 or 5 formatted shortcut and converts it to a full filename.
  AfxFullPath()—Pretty straightforward function here. This function turns a file path into an absolute (network and all) file path.
  AfxGetRoot()—Not so straightforward! Takes the volume out of a Uniform Naming Convention (UNC) path and returns the root name of the file. The UNC is formatted like this: \\server\share
  AfxComparePath()—Compares two paths and determines if they are identical.
  AfxGetFileTitle()—Returns just the filename from a path declaration.

You now have a pretty good understanding of what CFile really is. However, most applications don’t specifically use CFile, but one of the derived CFile classes, such as CStdioFile, or CMemFile.

The CStdioFile Class

Because CStdioFile derives from CFile, there is no need to look at functions that you might be already familiar with. CStdioFile provides a mechanism to process stream-based file data. It adds two new functions for processing this data: ReadString() and WriteString(). The ReadString() function, shown in Listing 21.3, will read in a string until it encounters one of the following conditions:

  Specific number of characters—If you pass in the number of characters to read (not including the string’s NULL terminator), the ReadString function will continue reading until this number is read or one of the other conditions is met, whichever comes first.
  End-of-line (newline)—If the ReadString encounters a CR/LF pair, it will stop reading. This happens even if the specified number of characters has not been read in.
  End-of-file—If the ReadString encounters the end of the file, it will not be able to process the stream any further.


Note:  

Remember that stream processing essentially is processed in a sequential fashion until an end character is encountered. By specifying the number of characters to process, the stream is read a character at a time, and a character count is applied. If an ending character has not been reached, and the count has, the ReadString function will terminate the stream with an end-of-line character.


If you were to dive into the code for CStdioFile, you would notice quite a bit of processing above and beyond CFile specifically dealing with data buffering. Because CFile is generic and concerns itself primarily with nonbuffered data, CStdioFile must implement the buffering necessary to handle stream input and output.

Because CStdioFile processes stream data, it makes sense for it to wrap the C runtime library file-handling routines (fstream, fopen, and so on). The declaration for CStdioFile lives within AFX.H, whereas its implementation resides in FILETXT.CPP. The ReadString implementation is represented in Listing 21.3, which resides in FILETXT.CPP.

Listing 21.3 The CStdioFile::ReadString Implementations


LPTSTR CStdioFile::ReadString(LPTSTR lpsz, UINT nMax)
{
    ASSERT(lpsz != NULL);
    ASSERT(AfxIsValidAddress(lpsz, nMax));
    ASSERT(m_pStream != NULL);

    LPTSTR lpszResult = _fgetts(lpsz, nMax, m_pStream);
    if (lpszResult == NULL && !feof(m_pStream))
    {
        clearerr(m_pStream);
        AfxThrowFileException(CFileException::generic,
        Ä_doserrno, m_strFileName);
    }
    return lpszResult;
}

BOOL CStdioFile::ReadString(CString& rString)
{
    ASSERT_VALID(this);

    rString = &afxChNil;    // empty string without deallocating
    const int nMaxSize = 128;
    LPTSTR lpsz = rString.GetBuffer(nMaxSize);
    LPTSTR lpszResult;
    int nLen = 0;
    for (;;)
    {
        lpszResult = _fgetts(lpsz, nMaxSize+1, m_pStream);
        rString.ReleaseBuffer();

        // handle error/eof case
        if (lpszResult == NULL && !feof(m_pStream))
        {
            clearerr(m_pStream);
            AfxThrowFileException(CFileException::generic,
                doserrno, m_strFileName);
        }

        // if string is read completely or EOF
        if (lpszResult == NULL ||
            (nLen = lstrlen(lpsz)) < nMaxSize ||
            lpsz[nLen-1] == ‘\n’)
            break;

        nLen = rString.GetLength();
        lpsz = rString.GetBuffer(nMaxSize + nLen) + nLen;
    }

    // remove ‘\n’ from end of string if present
    lpsz = rString.GetBuffer(0);
    nLen = rString.GetLength();
    if (nLen != 0 && lpsz[nLen-1] == ‘\n’)
        rString.GetBufferSetLength(nLen-1);

    return lpszResult != NULL;
}


Notice the use of standard C runtime library functions.

Some time ago, during a project where I had implemented a parsing function using ReadString(), I ran across a particularly annoying problem. Listing 21.4 is a macro that I wrote to get around that problem.

Listing 21.4 The MFC_CSTR_KLUDGE Macro


// -STUMBLED on the fact that the MFC code for
// StdioFile::ReadString will fail when you have
// a newline character in a location that is a
// multiple of the MFC_BUFLENGTH.  (go figure — )
// This stuffs a character in that location and then
// adds a newline character... ** SuperKludge !!! **
/////////////////////////////////////////////////////////////////
#define MFC_BUFLENGTH 128
#define MFC_CSTR_KLUDGE(s)                                      \
   {                                                            \
      if (s.GetLength() % MFC_BUFLENGTH == (MFC_BUFLENGTH - 1)) \
         { s = s + “\255\n”; }                                  \
      else                                                      \
         { s = s + “\n”; }                                      \
   }


Notice the comment in the first six lines. It tells the story about this annoying little bug. If for some reason a newline is encountered in the location pointed to by the maximum buffer length defined in ReadString, ReadString loses the newline character. Go back to Listing 21.3 for a minute. The line const int nMaxSize = 128; defines a maximum buffer length of 128. At first glance, you might be wondering why. Because you are working in a buffered mode, you want to buffer in chunks, hence the value. The GetBuffer function called in the line LPTSTR lpsz = rString.GetBuffer(nMaxSize); requires this chunk size. You process the chunk and then check to see if your running count has been obtained, or whether you hit an end-of-line or an end-of-file. By looking at the line lpsz = rString.GetBuffer(nMaxSize + nLen) + nLen;, you see that the for loop continues if the total size is not met. The line (nLen = lstrlen(lpsz)) < nMaxSize || contains the annoying problem. This is not really a bug, but if you are processing for the newline during a parse operation, you might encounter this. If the newline character happens to fall at location 128 of the buffer, it gets chopped off, and the ReadString finishes the loop without returning it to you (ouch!).



The CMemFile Class

Because I discussed CStdioFile in some detail, I think it only fair for those of you having to deal with shared memory processing to discuss the CMemFile class. However, you might be disappointed to find out that CMemFile doesn’t deal directly with shared memory. That’s up to you!

The primary reason that CMemFile is provided is so the user can write out, or serialize, a chunk of memory. Memory files are like disk files except that the file is stored in memory rather than on a disk. However, it’s nice to take advantage of file processing techniques when handling memory chunks. This keeps the user from having to manage the memory allocation each time a buffer is written to the memory. These chunks of memory can later be processed out to a file, or to the Registry. It can also be useful in transferring memory chunks between processes.


Note:  

It’s important to note here that dealing with memory is not exactly like dealing with disk files. The CMemFile member variable, m_hFile, is always null because of this fact. Because a file contains a handle for processing by the operating system, it must use this handle for opening and closing, and reading and writing. The same is not true for memory.

CMemFile does support Read() and Write(), but not the Open() and Close() functions.


CMemFile memory processing is similar in some respects to the CArray processing discussed in Chapter 20, “Strings and Collections.” The memory is grown when needed, and then shrunk when not needed. To grow the memory needed, all functions first check to see that they have enough memory for the operation. If not, they then call GrowFile() to add the needed memory.

The CSharedFile Class

This newly documented class is derived from CMemFile and provides wrappers for the global memory API functions. Shared memory files differ from process memory files in that the memory is allocated by the GlobalAlloc() Windows function. This global chunk can be shared using the Clipboard, or other OLE/COM uniform data transfer operations.

GlobalAlloc returns an HGLOBAL handle rather than a pointer to memory. HGLOBAL handle is used in certain applications, such as the Clipboard.


Note:  

CSharedFile does not use memory-mapped files as one might expect. The data cannot be shared between processes either. This might seem out of the ordinary, but it is done this way to allow growing of the memory area.


The implementation for CSharedFile is located in the FILESHRD.CPP file. If you have an opportunity to look at this file, notice that it contains global allocation function calls.

The CFileDialog Class

You’ve taken a quick tour of what MFC provides in the way of file processing, but getting your application to enable their usage is another subject. With every user interface application that stores data to a file, you must provide some mechanism to allow the user to select, open, or close the application’s file. MFC provides the CFileDialog class for just this functionality. As you can probably tell from the name, it is derived from the CDialog class.

The CFileDialog class is referred to as a common dialog class. This simply means that the Windows common API functions are grouped and wrapped by these dialog classes, giving them a dialog interface.

The CFileDialog class contains a structure that describes the settings for the dialog wrapper. The OPENFILENAME structure is used to define the CFileDialog settings, and the class wraps the GetOpenFileName() API function.

The CFileDialog is implemented in DLGFILE.CPP. Listing 21.5 shows the DoModal function for CFileDialog.

Listing 21.5 CFileDialog::DoModal() Implementation


int CFileDialog::DoModal()
{
    ASSERT_VALID(this);
    ASSERT(m_ofn.Flags & OFN_ENABLEHOOK);
    ASSERT(m_ofn.lpfnHook != NULL); // can still be a user hook

    // zero out the file buffer for consistent parsing later
    ASSERT(AfxIsValidAddress(m_ofn.lpstrFile, m_ofn.nMaxFile));
    DWORD nOffset = lstrlen(m_ofn.lpstrFile)+1;
    ASSERT(nOffset <= m_ofn.nMaxFile);
    memset(m_ofn.lpstrFile+nOffset, 0, (m_ofn.nMaxFile-nOffset)*
    Äsizeof(TCHAR));

    // WINBUG: This is a special case for the file open/save dialog,
    //  which sometimes pumps while it is coming up but before it has
    //  disabled the main window.
    HWND hWndFocus = ::GetFocus();
    BOOL bEnableParent = FALSE;
    m_ofn.hwndOwner = PreModal();
    AfxUnhookWindowCreate();
    if (m_ofn.hwndOwner != NULL && ::IsWindowEnabled(m_ofn.hwndOwner))
    {
        bEnableParent = TRUE;
        ::EnableWindow(m_ofn.hwndOwner, FALSE);
    }

    _AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
    ASSERT(pThreadState->m_pAlternateWndInit == NULL);

    if (m_ofn.Flags & OFN_EXPLORER)
        pThreadState->m_pAlternateWndInit = this;
    else
        AfxHookWindowCreate(this);

    int nResult;
    if (m_bOpenFileDialog)
        nResult = ::GetOpenFileName(&m_ofn);
    else
        nResult = ::GetSaveFileName(&m_ofn);

    if (nResult)
        ASSERT(pThreadState->m_pAlternateWndInit == NULL);
    pThreadState->m_pAlternateWndInit = NULL;

    // WINBUG: Second part of special case for file open/save dialog.
    if (bEnableParent)
        ::EnableWindow(m_ofn.hwndOwner, TRUE);
    if (::IsWindow(hWndFocus))
        ::SetFocus(hWndFocus);

    PostModal();
    return nResult ? nResult : IDCANCEL;
}


The lines

if (m_bOpenFileDialog)
    nResult = ::GetOpenFileName(&m_ofn);
else
    nResult = ::GetSaveFileName(&m_ofn);

are the important section to inspect here. The m_ofn member structure is the OPENFILENAME structure contained within the CFileDialog. The OPENFILENAME structure is a Windows API structure, and is defined as shown in Listing 21.6.

Listing 21.6 The tagOFN Structure


typedef struct tagOFN { // ofn
    DWORD         lStructSize;
    HWND          hwndOwner;
    HINSTANCE     hInstance;
    LPCTSTR       lpstrFilter;
    LPTSTR        lpstrCustomFilter;
    DWORD         nMaxCustFilter;
    DWORD         nFilterIndex;
    LPTSTR        lpstrFile;
    DWORD         nMaxFile;
    LPTSTR        lpstrFileTitle;
    DWORD         nMaxFileTitle;
    LPCTSTR       lpstrInitialDir;
    LPCTSTR       lpstrTitle;
    DWORD         Flags;
    WORD          nFileOffset;
    WORD          nFileExtension;
    LPCTSTR       lpstrDefExt;
    DWORD         lCustData;
    LPOFNHOOKPROC lpfnHook;
    LPCTSTR       lpTemplateName;
} OPENFILENAME;


Because this structure is available, you can see that dialog items for CFileDialog are easily changed. Let’s take a look at some things you need to do to “roll your own” CFileDialog class.



The User-Defined CFileDialog Class

Because m_ofn is readily accessible, you might think that you simply set the attributes that you are interested in and you are ready to go. However, there is more to it than that. The following list is a step-by-step guide to creating your own CFileDialog:

1.  Copy the standard resource to your project’s resource directory and add it to your project. This is a good place to start if you’re not familiar with creating dialogs. You can choose to create a completely different look and feel instead of changing the standard.
2.  Copy the DLGS.H header file to your project. To use the dialog that you are modifying, you will need this file.
3.  Create code for any specific functionality. When you have a dialog, simply add the message handler functions to perform the specific steps that you need.
4.  Adjust the m_ofn structure. Now you’re ready to modify this structure and make it work for you. Do this by setting the hInstance member by calling AfxGetResourceHandle(). To set the template name (lpTemplateName member), call MAKEINTRESOURCE and pass it the IDD of the dialog. Next, set the Flags member of the structure to indicate the OFN_ initialization.
5.  Override OnInitDialog. Because your new dialog is a dialog-derived class, you will need to put in any special initialization code. Don’t forget to call CDialog::OnInitDialog()!


Note:  

You will notice that the m_ofn structure can be set to look like the Explorer dialogs by defining OFN_EXPLORER. This is a nice change, but if you are developing applications that use the old style (Windows 3.1 or NT 3.51), you probably would not want to use this.

When you “roll your own” File dialog box, the OFN_EXPLORER bit actually uses the template that you supply to add to the existing dialog box. In other words, if you specify OFN_EXPLORER, you are adding to the supplied dialog and not creating new!


Practical Usage of CFile and CFileDialog

You’ve taken an in-depth “whirlwind” tour of CFile and CFileDialog and your head is swimming right now. You probably want to look at some practical examples to get a handle on all this information.

Opening a File

First, your application should allow the user to select the file. This is done with the CFileDialog.

Listing 21.7 is the CMainFrame::OnFileOpen() function for the UNL_MultiEd application. This function will display a CFileDialog to allow the user to select an EventRecorder document. If none exist, or he wants to create new, the user simply cancels and the EventTally CFormView is displayed. This application is detailed in Chapter 20.

Listing 21.7 The UNL_MultiEd CMainFrame::OnFileOpen() Function


01:    void CMainFrame::OnFileOpen()
02:    {
03:        CString sFileName;
04:
05:        CUNL_MultiEdApp* pApp = (CUNL_MultiEdApp*)AfxGetApp();
06:
07:        CXEventRecorder *ourEventRecorder = pApp->GetEventRecorder();
08:
09:        static TCHAR szFilter[] =
           Ä_T(“Event Recorder Files (*.erx)|*.erx|AllFiles(*.*)|*.*|”) ;
10:
11:        //////////////////////////////////////////////////////////
12:        //  Define the common dialog for opening the file..     //
13:        //////////////////////////////////////////////////////////
14:        CFileDialog dlg( TRUE, _T(“*.erx”), NULL,
15:                    OFN_HIDEREADONLY | OFN_PATHMUSTEXIST,
                       ÄszFilter,NULL)  ;
16:
17:        dlg.m_ofn.lpstrTitle = “Event Recorder File”;
18:        dlg.m_ofn.lpstrInitialDir =
           ÄCString(“C:\\EventRecorderFiles\\”);
19:
20:        /***********************************************************
21:        ** Here you want to give them the chance to use an ASCII  **
22:        ** delimited file to load events and participants. Open   **
23:        ** Event Tally document to let them continue working with **
24:        ** events. If they cancel, start with participant’s view. **
25:        ***********************************************************/
26:
27:       if (IDOK != dlg.DoModal())
28:       {
29:
30:        POSITION curTemplatePos = pApp->GetFirstDocTemplatePosition();
31:
32:            // Start with the Participants view..
33:        while(curTemplatePos != NULL)
34:            {
35:            CDocTemplate* curTemplate =
36:                pApp->GetNextDocTemplate(curTemplatePos);
37:            CString str;
38:            curTemplate->GetDocString(str, CDocTemplate::docName);
39:            if(str == _T(“PartForm”))
40:            {
41:                   CPartDoc* pPartDoc;
42:            pPartDoc = (CPartDoc*)curTemplate->OpenDocumentFile(NULL);
43:            return;
44:            }
45:            }
46:        }
47:        else
48:        {
49:        sFileName = dlg.GetPathName();
50:
51:        //  Object not created yet, so create it now.
52:          if (ourEventRecorder == NULL)
53:            {
54:                ourEventRecorder = new CXEventRecorder();
55:                 pApp->SetEventRecorder(ourEventRecorder);
56:             }
57:
58:            //Load in the Event information::
59:            ourEventRecorder->LoadEvents(sFileName);
60:
61:        POSITION curTemplatePos = pApp->GetFirstDocTemplatePosition();
62:
63:            // Start with the Participants view..
64:        while(curTemplatePos != NULL)
65:            {
66:            CDocTemplate* curTemplate =
67:                pApp->GetNextDocTemplate(curTemplatePos);
68:            CString str;
69:            curTemplate->GetDocString(str, CDocTemplate::docName);
70:            if(str == _T(“EventTallyForm”))
71:            {
72:                   CEventTallyDoc* pEVTallyDoc;
73:              pEVTallyDoc =
        Ä(CEventTallyDoc*)curTemplate->OpenDocumentFile(NULL);
74:            return;
75:            }
76:           }
77:        }
78:    }

Notice the setup for the CFileDialog. Line 9 contains what is known as a extension filter. This extension filter is used to filter file types that contain only the supplied filter. For example, when you open a Microsoft Word document, you see only the files having .DOC as an extension. In this case, you want to see the files with an .ERX extension, which represents an ASCII delimited form file (explained in Chapter 20). You then create the CFileDialog (see lines 14 and 15) with the filter applied. At this point, you could go on your merry way, but it wouldn’t be yours without modifying the m_ofn structure to your liking. Lines 17 and 18 will apply your own title and initial directory to the dialog box.


Note:  

At this point, you aren’t really rolling your own CFileDialog. The m_ofn structure is easily modified with standard items to obtain most of the functionality you will ever need for CFileDialog.


If the user selects Cancel from the dialog, you will pull up the Participant view for him to enter participants, and essentially create a new EventRecorder file. However, if he wants to load the events and participants from the file, you get the information from the CFileDialog and pass that on to the LoadEvents() function of the CXEventRecorder class.



Reading Data from a File

In the UNL_MultiEd application, you read in an Events file (.ERX) that contains event and participant information. The Events are in the file in the manner shown in Listing 21.8. The code in Listing 21.8 is from an Events (.ERX) file supplied with the UNL_MultiEd sample application.

Listing 21.8 The UNL_MultiEd Events File (DUMMY.ERX)


EVENT:
Broad Jump
Pole Vault
Hot Shots
Distance Run


The participants for the events are also in the same file, and are delimited by the format shown in Listing 21.9.

Listing 21.9 The UNL_MultiEd Events File (CONTINUED)


PARTICIPANT:
Scribner III, Kenn, CC-2
White, Dave, CC-2
Heyman, Bill, CC-1
Jonstone, Nancy, ST-4
Marshall, Mary, ST-3
Jones, William, AB-1
Alson, Abraham, AB-1


Although this file structure is quite simplistic, it serves your purpose. The string manipulation is covered in Chapter 20, but let’s take a look at what is taking place when the file contents are read into memory. Listing 21.10 contains the actual file parsing routine that you need to investigate. The filename is passed into this function.

Listing 21.10 The CXEventRecorder::LoadEvents Function


01:    void
02:    CXEventRecorder::LoadEvents(CString sFileName)
03:    {
04:        BOOL    bDO_EVENT           = FALSE;
05:        BOOL    bDO_PARTICIPANT     = FALSE;
06:
07:        CStdioFile file;
08:        CString line;
09:
10:        if (file.Open(sFileName,CFile::modeRead))
11:        {
12:        while (file.ReadString(line))
13:        {
14:                if (line == “EVENT:”)
15:                {
16:                    bDO_EVENT       = TRUE;
17:                    bDO_PARTICIPANT = FALSE;
18:                }
19:                else if (line == “PARTICIPANT:”)
20:                {
21:                    bDO_EVENT       = FALSE;
22:                    bDO_PARTICIPANT = TRUE;
23:                }
24:            else
25:                {
26:            if (bDO_EVENT)
27:                   {
28:                        CXEvent *ourEvent = new CXEvent();
29:                        ourEvent->SetEventName(line);
30:
31:                        if (m_pEvents.IsEmpty())
32:                            m_pEvents.SetAt(0,ourEvent);
33:                 else
34:                m_pEvents.SetAt(m_pEvents.GetCount(),ourEvent);
35:            }
36:
37:            if (bDO_PARTICIPANT)
38:                   {
39:                        CXParticipant *ourParticipant =
                           Änew CXParticipant();
40:                        CString sLastName,sFirstName,sTeam;
41:
42:                        CStringList slParts;
43:                        SplitPartsString(line, slParts, “,”);
44:
45:                        // The first token should be the last name
46:                        POSITION pPartPos = slParts.FindIndex(0);
47:                        sLastName = slParts.GetAt(pPartPos);
48:
49:                        // Next you have the first name..
50:                        pPartPos = slParts.FindIndex(1);
51:                        sFirstName = slParts.GetAt(pPartPos);
52:
53:                        //Next you have the Team name
54:                        pPartPos = slParts.FindIndex(1);
55:                        sTeam = slParts.GetAt(pPartPos);
56:
57:                        ourParticipant->SetLastName(sLastName);
58:                        ourParticipant->SetFirstName(sFirstName);
59:                        ourParticipant->SetTeam(sTeam);
60:
61:                        m_plParticipants.AddTail(ourParticipant);
62:                    }
63:                }
64:        }
65:        file.Close();
66:        }
67:
68:
69:    }


Lines 10 through 12 are the lines that you are concerned about here. Because this is a file parsing routine, there is no need to open the file with write permissions. Instead, you simply open the file with read access (see line 10) and then inside the while loop, you read each line. This loop will end when the end-of-file is met. After the while loop exits, you close the file (see line 65). Very simple! Even though this is a simple example, the functionality encapsulated by the CFile class provides a very flexible interface to implement file functions.

A Classical Approach

Still available to the developer is another approach: use of streams to persist data to a file system. Although you learned that MFC provides a fairly robust class for handling your file request, it is important to understand the traditional stream approach. I don’t give you any examples, but I will discuss the various aspects of using a non-MFC approach.

You might be asking yourself why a statement would be made to that effect in an MFC book. Quite frankly, MFC is not a “cure-all” for developing applications. If you are tasked with developing COM objects that require lightweight implementation, you can choose to use the ATL library. This approach gives you lightweight objects; however, you leave the convenient world of MFC. Knowing alternative approaches to MFC gives you the ability to perform the task without worry.

I leave it as an exercise to create a file-handling class using the fstream library functions. You might find that after you develop this class, you will begin to use it exclusively!



What Are Streams?

The fstream class is derived from iostream, which in turn is derived from istream and ostream, and is specialized for combined disk file input and output. The fstream constructors create and attach a buffer used specifically for handling file input and output. This buffer is the filebuf buffer object.

The filebuf class is derived from streambuf. It is specifically designed for buffered disk file I/O. The file stream classes, ofstream, ifstream, and fstream, use member functions of filebuf to fetch and store characters.

The streambuf class contains three specific areas for handling data streams; the reserve area, put area, and get area. The derived filebuf objects use the put area and the get area. In fact, their pointers are tied together, so whenever one is moved, the other automatically moves with it.

Although the filebuf object’s get and put pointers are tied together, it doesn’t mean that both areas are active at the same time. In fact, they are independent of one another. Now you are totally confused. What I am saying here is that the get area and the put area are not active at the same time. During the input mode, the get area is active and will contain data. The reverse is true during output. When the mode switches, the active buffer will clear its contents, and the other buffer will then be ready for handling the stream. Thus, either the get pointer or the put pointer is null at all times. If both are null, you have problems!

Okay, you’re getting swamped with information. Streams are nothing more than a series of data bits. These buffers are designed to organize the streams of data into cohesive, workable units. Are you ready to dig a little deeper?

The istream and ostream classes define the characteristics for input and output data. Input data can come from files, input terminal, and or over the network. The data is streamed in serially, thus the term stream. These streams are usually defined by starting and ending points, and will also contain a check character(s) at the end to verify the integrity of the data. This stream-handling functionality is maintained at the ostream and istream level.

The fstream class is derived from the iostream object, which is multiply derived from istream and ostream. Therefore, the very low-level handling of the data is actually handled at the lowest level. The fstream class then becomes responsible for handling the file handle architecture (operating-system-specific) that will process the creation, opening, closing, and destruction of data files.

Although MFC encapsulates all this functionality in the CFile classes, it would be a good idea to understand the basics. In fact, if you are a brave soul, you might want to roll your own iostream classes to define specific file input and output. Remember, MFC doesn’t have all the answers. It was designed to create generic usable classes in order to create workable solutions. It is not a “cure-all” for handling user interface solutions.

Summary

The CFile class and its internals were spelled out for you in this chapter. This powerful utility class provides the basis for managing many different types of file manipulations. In this chapter, you took a close look at CStdioFile, which will probably be a staple of your programming repertoire. You also learned the steps that it takes to “roll your own” CFileDialog. Armed with this knowledge, you should be ready to tackle your application’s file I/O requirements with ease, or at least have a better understanding of the MFC (and C++) file I/O mechanisms.